Add a fine-grained text splice op#346
Open
johannesmutter wants to merge 1 commit into
Open
Conversation
Text edits previously recorded ['set', path, whole_value] twice per keystroke (ops + inverse_ops), so history stored a full copy of the paragraph per keystroke and the op stream carried no meaning beyond "content changed". Transaction.splice(path, start, delete_count, text) records ['splice', [node_id, prop], start, delete_count, text] instead. Mark/annotation range adjustment happens inside op application (splice_annotated_text in doc_utils.ts, the single source of truth used by forward and inverse ops alike), and range nodes whose range collapsed away are garbage-collected ref-count-aware, like set() does. Pure insertions (every keystroke) take a fast path: insertion adjustment can never remove or truncate ranges, so the inverse is a plain splice by construction and no value copies or round-trip checks run on the hot path. Deletions verify losslessness with a round-trip check; lossy deletions carry the prior ranges verbatim as a 6th op element, so undo is always exact. insert_text and delete_selection (text branch) now emit splices; their public behavior is unchanged, and plain typing returns early before the mark/annotation restoration copies. History batching coalesces consecutive contiguous typing splices into one op using the inverses' stored lengths (no re-segmentation of the accumulated batch text), so a one-second typing batch stores a single small op instead of one string copy per keystroke. Character offsets are grapheme clusters (Intl.Segmenter), the same unit as all existing svedit text offsets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
michael
marked this pull request as draft
July 22, 2026 10:29
michael
marked this pull request as ready for review
July 22, 2026 10:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 2/4 of the op-engine series (#335). Stacked on
op-engine/1-commit-funnel.Transaction.splice(path, start, delete_count, text)records['splice', [node_id, prop], start, delete_count, text]instead of a whole-value copy per keystroke;insert_text/delete_selectionemit splices with unchanged public behaviorsplice_annotated_text(doc_utils), the single source of truth for forward and inverse ops; collapsed range nodes are cascade-GC'd likeset()doesOpen question for your review (RFC question 2 in #335): the inverse currently has two shapes — a plain 5-element splice when lossless, a 6th prior-ranges element when lossy. The alternative is one shape that always carries prior ranges. Happy to change based on your take.
Tests: 10 for this PR.
🤖 Generated with Claude Code